#include "scheduler.h"

/**
 * Funcao para encapsular a criacao de um processo filho e a redefinicao de
 * sua prioridade.
 * @param arg_list	Lista de argumentos semelhante a 'argv' para a funcao posix_spawn
 * @param path		Caminho completo do programa a ser executado pelo processo filho
 * @param deadline	Deadline da tarefa
 * @return pid_t	Process ID do processo criado
 */
pid_t create_task(char* const arg_list[], const char* path, int deadline)
{
	int child = 0, st, prio;
	short min_prio = 19, max_prio = -20;

	if(deadline-30 > min_prio)
		prio = 17;
	else if(deadline-30 <= max_prio)
		prio = -6;
	else
		prio = deadline - 30;

	// Cria processo filho inicialmente com mesma politica de escalonamento e prioridade
	st = posix_spawn(&child, path, NULL, NULL, arg_list, NULL);

	// Redefinicao da prioridade do processo filho
	st = setpriority(PRIO_PROCESS, child, prio);
	if(st < 0)
	{
		perror("setpriority");
		exit(-1);
	}

	return child;
}

int cont = 0;
void handler(int signum)
{
	cont++;
}

/**
 * Define um timer ciclico para a aplicacao.
 */
void my_settimer()
{
	timer_t timerid;
	struct sigevent sev;
	struct itimerspec its;

	signal( SIGRTMIN, handler );

	sev.sigev_notify = SIGEV_SIGNAL;
	sev.sigev_signo = SIGRTMIN;
	sev.sigev_value.sival_ptr = &timerid;
	if (timer_create(CLOCK_REALTIME, &sev, &timerid) == -1)
	{
	   perror("timer_create");
		exit(-1);
	}

	/* Start the timer */
	its.it_value.tv_sec = 0L;
	its.it_value.tv_nsec = 100000000L;
	its.it_interval.tv_sec = its.it_value.tv_sec;
	its.it_interval.tv_nsec = its.it_value.tv_nsec;

	if (timer_settime(timerid, 0, &its, NULL) == -1)
	{
		perror("timer_settime");
		exit(-1);
	}
}

/**
 * Funcao de comparacao passada como argumento para o qsort.
 */
int compare(const void* a, const void* b)
{
	if(((struct task_t*) a)->deadline < ((struct task_t*) b)->deadline)
		return -1;
	else if (((struct task_t*) a)->deadline == ((struct task_t*) b)->deadline)
		return 0;
	else
		return 1;
}

/**
 * Verificacao dos parametros de execucao.
 * @param argc		Contagem do numero de parametros de execucao
 */
struct task_t* treat_execparams(int argc, char** argv)
{
	if(argc < 2 || argc%2 != 0)
	{
		printf("Uso: ./scheduler <my_prio> <path_prog1> <deadline_prog1> ... <path_progn> <deadline_progn>\n");
		printf("Os caminhos para os programas devem ser completos.\n");
		exit(-1);
	}

	int i, ntasks;
	ntasks = (argc/2) - 1;
	struct task_t *tasks;
	tasks = (struct task_t*) malloc(sizeof(struct task_t) * ntasks);
	for(i = 0; i < ntasks; i++)
	{
		struct task_t tsk;
		tsk.path = argv[2*i + 2];
		tsk.deadline = atoi(argv[2*i + 3]);
		tasks[i] = tsk;
	}

	qsort(&tasks[0], ntasks, sizeof(struct task_t), &compare);

	return tasks;
}

/**
 * Cria uma area de memoria compartilhada que sera usada pelos processos filhos
 * na comunicacao (passagem de dados de um para outro).
 * @param shmid		Preenchido com o identificador da area criada
 */
void create_shdmem(int *shmid)
{
	char* shm;
	key_t key = KEY_SHDMEM;

	// Cria area de memoria compartilhada (create & read-write)
	if ((*shmid = shmget(key, sizeof(int)*TAMANHO_CUSTOS*TAMANHO_CUSTOS, IPC_CREAT | 0666)) == -1)
	{
   		perror("shmget"); 
		exit(-1);
	}

	// Anexa o processo corrente a area recem criada
	if ((shm = shmat(*shmid, NULL, 0)) == (char *) -1) 
	{
        	perror("shmat");
	        exit(-1);
	}
}

/**
 * Programa principal.
 */
int main (int argc, char** argv)
{
	int st, shmid, i;
	int ntasks;
	pid_t child = 0;
	struct sched_param param;
	struct shmid_ds buf;
	struct task_t* edf_vec;

	memset(&buf, 0, sizeof(buf));

	// Verificacoes e inicializacoes
	edf_vec = treat_execparams(argc, argv);
	my_settimer();
	create_shdmem(&shmid);

	ntasks = ((argc/2) - 1);

	// Redefinicao da politica de escalonamento
	param.sched_priority = atoi(argv[1]);
	st = sched_setscheduler(0, SCHED_FIFO, &param);

	// Redefinicao da prioridade do processo
	st = setpriority(PRIO_PROCESS, child, atoi(argv[1]));
	if(st < 0)
	{
		perror("setpriority");
		exit(-1);
	}

	printf("scheduler: %d\n", sched_getscheduler(0));

	if (st < 0)
	{
		perror("setscheduler");
		exit(-1);
	}

	int flags = MCL_CURRENT;
	mlockall(flags);

	// Executa cada tarefa por ordem de deadline (menor deadline primeiro)
	i = 0;
	while(i < ntasks)
	{
		char* const arg_list[] = { edf_vec[i].path, NULL };
		child = create_task(arg_list, edf_vec[i].path, edf_vec[i].deadline);

		pid_t id = waitpid(child, NULL, WUNTRACED);
		if(id < 0)
			perror("waitpid");

		i++;
	}

	// Remove segmento de memoria compartilhado
	shmctl(shmid, IPC_RMID, &buf);

	munlockall();

	return 0;
}

